package maple.core.gateways;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
import java.util.logging.Level;
import java.util.TreeMap;

import jade.domain.DFService;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAException;

import maple.core.Gateway;
import maple.lang.Command;
import maple.lang.Language;
import maple.util.Profile;
import maple.util.Timestamp;

public class MobilePhidget extends Gateway
{
	private final int DELAY = 5 * 60 * 1000;
	private final String DEVICE = "PhidgetInterfaceKit_%";
	
	private Connection _database;
	private String _table_name;
	
	private enum Port{ A, DI };
	
	public MobilePhidget()
	{
		super( 1 );
		
		_database = null;
	}

	protected void setup()
	{
		super.setup();

		if( !connect( _arguments[ 0 ].toString() ) )
		{
			doDelete();
		}
	}

	protected void takeDown()
	{
		super.takeDown();

		try
		{
			// close the database connection
			if( _database != null )
			{
				_database.close();
			}
		}
		catch( SQLException e )
		{
			_logger.log( Level.WARNING, "", e );
		}
	}

	protected void check()
	{
		super.check();

		TreeMap< String, Profile > sensors = query();
		if( isChanged( sensors ) )
		{
			_sensors = sensors;
			
			try
			{
				DFAgentDescription agent = getInstance( _names, _types, null, null, null );
				for( String port : _sensors.keySet() )
				{
					Profile profile = _sensors.get( port );

					String[] tokens = splitPort( port );
					agent.addServices( profile.toService( getName(), Language.Instruction.READ, port ) );
				}
				DFService.modify( this, agent );
			}
			catch( FIPAException e )
			{
				_logger.log( Level.WARNING, "", e );
			}
		}
	}

	/**
	 * return true if connecting is successful
	 *
	 * @param	file_name	the name of the specified file
	 * @return	true if connecting is successful
	 *
	 * @author	snowfoxy
	 * @since	2012-03-01
	 */
	private boolean connect( final String file_name )
	{
		try
		{
			// load the configuration of the database
			BufferedReader reader = new BufferedReader( new FileReader( file_name ) );
			String line = reader.readLine();
			reader.close();

			if( line != null )
			{
				String[] tokens = line.split( Language.DELIMITER );
				if( tokens.length >= 5 )
				{
					// connect to database
					Class.forName( "com.mysql.jdbc.Driver" );
					_database = DriverManager.getConnection( "jdbc:mysql://" + tokens[ 0 ] + ":3306/" + tokens[ 1 ], tokens[ 2 ], tokens[ 3 ] );
					_table_name = tokens[ 4 ];
					return true;
				}
			}
		}
		catch( ClassNotFoundException e )
		{
			_logger.log( Level.WARNING, "", e );
		}
		catch( IOException e )
		{
			_logger.log( Level.WARNING, "", e );
		}
		catch( SQLException e )
		{
			_logger.log( Level.WARNING, "", e );
		}
		return false;
	}

	/**
	 * return true if adding the specified port with the specified sensitivity successful
	 *
	 * @param   port		the specified port
	 * @param   sensitivity the specified sensitivity
	 * @return  true if adding the specified port with the specified sensitivity successful
	 *
	 * @author  snowfoxy
	 * @since   2012-03-01
	 */
	protected boolean insertPort( final String port, final String sensitivity )
	{
		return false;
	}

	/**
	 * return true if the sensors are changed
	 *
	 * @param	sensors	the new sensor list
	 * @return	true if the sensors are changed
	 *
	 * @author	snowfoxy
	 * @since	2012-03-01
	 */
	private boolean isChanged( final TreeMap< String, Profile > sensors )
	{
		for( String key: sensors.keySet() )
		{
			if( !_sensors.containsKey( key ) )
			{
				return true;
			}
			else
			{
				if( !sensors.get( key ).toString().equals( _sensors.get( key ).toString() ) )
				{
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * return the status of modifying the services
	 *
	 * @return	the status of modifying the services
	 *
	 * @author	snowfoxy
	 * @since	2012-03-01
	 */
	protected Command.Message modify()
	{
		return Command.Message.BAD_COMMAND;
	}

	private TreeMap< String, Profile > query()
	{
		TreeMap< String, Profile > sensors = new TreeMap< String, Profile >();
		try
		{
			PreparedStatement statement = _database.prepareStatement( "SELECT DISTINCT gateway, port, sensor, location FROM " + _table_name + " WHERE sensed_time > ? AND gateway LIKE ? LIMIT 100" );
			statement.setLong( 1, Long.parseLong( Timestamp.Default.format( new Date( System.currentTimeMillis() - DELAY ) ) ) ); 
			statement.setString( 2, DEVICE ); 
			ResultSet results = statement.executeQuery();
			while( results.next() )
			{
				TreeMap< String, String > properties = new TreeMap< String, String >();
				String port = results.getString( "gateway" ) + "." + results.getString( "port" );
				properties.put( Profile.Property.PORT.toString(), port );
				properties.put( Profile.Property.TYPE.toString(), results.getString( "sensor" ) );
				properties.put( Profile.Property.LOCATION.toString(), results.getString( "location" ) );
				properties.put( Profile.Property.SENSITIVITY.toString(), "10" );
				Profile profile = new Profile( properties );
				sensors.put( port, profile );
			}
		}
		catch( SQLException e )
		{
			_logger.log( Level.WARNING, "", e );
		}

		return sensors;
	}

	private double queryValue( final String gateway, final String port )
	{
		try
		{
			PreparedStatement statement = _database.prepareStatement( "SELECT * FROM " + _table_name + " WHERE gateway = ? AND port = ? AND sensed_time > ? ORDER BY sensed_time DESC" );
			statement.setString( 1, gateway ); 
			statement.setString( 2, port ); 
			statement.setLong( 3, Long.parseLong( Timestamp.Default.format( new Date( System.currentTimeMillis() - DELAY ) ) ) ); 
			ResultSet results = statement.executeQuery();
			if( results.next() )
			{
				return results.getDouble( "value" );
			}
		}
		catch( SQLException e )
		{
			_logger.log( Level.WARNING, "", e );
		}
		return -1;
	}

	/**
	 * return the value of the specified port
	 *
	 * @param	port	the specified port
	 * @return	the value of the specified port
	 *
	 * @author	snowfoxy
	 * @since	2011-11-04
	 */
	protected double read( final String port )
	{
		double result = -1;
		String[] tokens = splitPort( port );
		if( tokens != null  )
		{
			//TODO: find
			result = queryValue( tokens[ 0 ], tokens[ 1 ] + "." + tokens[ 2 ] );
		}
		return result;
	}

	/**
	 * return the type and the index of the specified port
	 *
	 * @param   port    the specified port
	 * @return  the type and the index of the specified port
	 *
	 * @author  snowfoxy
	 * @since   2012-03-01
	 */
	private String[] splitPort( final String port )
	{
		String[] tokens = port.split( Gateway.DELIMITER );
		try
		{
			if( tokens.length == 3 )
			{
				Port.valueOf( tokens[ 1 ] );
				if( Integer.parseInt( tokens[ 2 ] ) >= 0 )
				{
					return tokens;
				}
			}
		}
		catch( IllegalArgumentException e )
		{
			_logger.log( Level.WARNING, "", e );
		}
		return null;
	}

	/**
	 * true if updating the specified port with the specified sensitivity successful
	 *
	 * @param   port		the specified port
	 * @param   sensitivity the specified sensitivity
	 * @return  true if modifying the specified port with the specified sensitivity successful
	 *
	 * @author  snowfoxy
	 * @since   2012-03-01
	 */
	protected boolean updatePort( final String port, final String sensitivity )
	{
		return false;
	}

	/**
	 * return the status of the write command with the specified port
	 *
	 * @param	port	the specified port
	 * @return	the status of the write command with the specified port
	 *
	 * @author	snowfoxy
	 * @since	2012-03-01
	 */
	protected Command.Message write( final String port )
	{
		return Command.Message.BAD_COMMAND;
	}
}
